Skip to content

fix(duckdb): separate extension install from load with best-effort lifecycle#530

Merged
cofin merged 7 commits into
mainfrom
fix/duckdb-thread-local
Jun 22, 2026
Merged

fix(duckdb): separate extension install from load with best-effort lifecycle#530
cofin merged 7 commits into
mainfrom
fix/duckdb-thread-local

Conversation

@cofin

@cofin cofin commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Fixes a regression where name-only DuckDB extensions were installed over the network on every physical connection (and raised on any failure). This separates install (one-time, network, on-disk) from load (per-connection, local) in DuckDBConnectionPool and restores a best-effort failure model.

Closes #529

What changed

  • Name-only {"name": X} is now LOAD-only — no network install on every connection.
  • Explicit install is triggered by install=True, force_install, or any of version / repository / repository_url.
  • Best-effort by default — install and load failures log at WARNING; required=True re-raises (and, for secrets, re-enables post-creation verification).
  • Installs are bounded once per pool per signature (name, version, repository, repository_url), lock-guarded, so explicit installs run once across reconnects and concurrent threads. force_install bypasses the cache; failed best-effort installs are not cached (they retry).
  • load_extension() runs per physical connection.
  • Secrets are best-effort by default with an optional required toggle. Identifier validation stays inside the try block, so malicious identifiers are never executed on either path.
  • New install / required keys on DuckDBExtensionConfig and required on DuckDBSecretConfig.

Tests

New tests/unit/adapters/test_duckdb/test_extension_lifecycle.py with call-count tests (spy connection, no network, no timing):

  • Name-only never installs; loads once.
  • Explicit install runs once across reconnects and once across 8 concurrent threads.
  • version / repository / repository_url imply install.
  • Load and install failures are best-effort by default and raise when required=True.

Existing strict-by-default secret tests moved to required=True; an obsolete "surfaces install errors" pool test was repurposed into a name-only load-only guard.

Verification

  • 84 DuckDB unit tests pass
  • 62 DuckDB integration tests pass
  • pyright sqlspec/adapters/duckdb/ clean
  • ruff check + ruff format clean

Note: also bundles a chore: bump version and deps commit.

cofin added 7 commits June 22, 2026 19:49
Add DuckDBExtensionConfig.install and .required and DuckDBSecretConfig.required
NotRequired keys to support the install/load split and best-effort failure model.

Refs: sqlspec-oag1.1.1
TDD red phase. Spy connection records install/load calls without network.
Behavior-change tests (name-only never installs, install once per pool,
concurrent install once, load/install best-effort) fail against current code;
version/repository-imply-install and required-raises pass as regression guards.

Refs: sqlspec-oag1.1.2
Name-only extensions are now LOAD-only (restores v0.49.1). Explicit install is
triggered by install=True, force_install, or version/repository/repository_url.
Install and load failures are best-effort (WARNING) unless required=True raises.
Install runs under the pool lock via _install_extension_once.

Repurpose the obsolete surfaces-install-errors pool test into a name-only
load-only regression guard.

Tests A/D/E green; install-once dedup (B/C) follows in sqlspec-oag1.1.4.

Refs: sqlspec-oag1.1.3
Track installed extensions by (name, version, repository, repository_url)
signature in a lock-guarded set so explicit installs run once per pool across
reconnects and concurrent threads. force_install bypasses the cache; failed
best-effort installs are not recorded so they retry on the next connection.

Tests B (install once across sessions) and C (concurrent install once) now green.

Refs: sqlspec-oag1.1.4
Secret creation is now best-effort by default: validation, CREATE SECRET, and
verification run inside a try; failure logs a WARNING unless required=True, which
also re-enables post-creation verification. Identifier validation stays inside
the try so malicious identifiers are never executed on either path.

Move the three strict-by-default secret tests to required=True and add
best-effort vs required coverage.

Refs: sqlspec-oag1.1.5
@codecov-commenter

codecov-commenter commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.91%. Comparing base (bc5b8f2) to head (0dcefba).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #530      +/-   ##
==========================================
+ Coverage   74.90%   74.91%   +0.01%     
==========================================
  Files         442      442              
  Lines       55120    55148      +28     
  Branches     8697     8697              
==========================================
+ Hits        41287    41316      +29     
- Misses      11149    11151       +2     
+ Partials     2684     2681       -3     
Flag Coverage Δ
integration 58.62% <19.56%> (-0.01%) ⬇️
py3.10 71.80% <100.00%> (+0.01%) ⬆️
py3.11 71.82% <100.00%> (+0.01%) ⬆️
py3.12 71.81% <100.00%> (+0.01%) ⬆️
py3.13 71.81% <100.00%> (+0.01%) ⬆️
py3.14 74.04% <100.00%> (+0.01%) ⬆️
unit 61.92% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sqlspec/adapters/duckdb/config.py 95.56% <100.00%> (+0.17%) ⬆️
sqlspec/adapters/duckdb/pool.py 79.03% <100.00%> (+1.74%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cofin cofin changed the title fix(duckdb): split extension install vs load + best-effort lifecycle (C1) fix(duckdb): separate extension install from load with best-effort lifecycle Jun 22, 2026
@cofin cofin merged commit e0277bd into main Jun 22, 2026
28 checks passed
@cofin cofin deleted the fix/duckdb-thread-local branch June 22, 2026 21:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DuckDB: name-only extensions install over the network on every connection

2 participants